package com.vains.controller;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.vains.config.properties.ServerProperties;
import com.vains.constant.GrantTypeConstant;
import com.vains.dto.request.OauthTokenReq;
import com.vains.entity.OauthClientDetails;
import com.vains.enumeration.HttpRequestMethod;
import com.vains.exception.CustomerException;
import com.vains.io.JsonUtils;
import com.vains.service.IOauthClientDetailsService;
import com.vains.system.model.Result;
import com.vains.system.util.ResultUtils;
import com.vains.utils.SpringContextUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;

/**
 * <p>
 * 用户表 前端控制器
 * </p>
 *
 * @author vains
 * @since 2020-07-03
 */
@Slf4j
@RestController
@AllArgsConstructor
@Api(tags = "oauth2 相关接口")
@RequestMapping("/oauth")
public class Oauth2Controller {

    private final PasswordEncoder passwordEncoder;

    private final ServerProperties serverProperties;

    private final IOauthClientDetailsService oauthClientDetailsService;

    @GetMapping("/login")
    @ApiOperation(value = "重新封装oauth2的接口实现", notes = "重新封装oauth2的接口实现")
    public Result<?> tokenGet(OauthTokenReq oauthTokenReq) {
        // 验证参数
        validation(oauthTokenReq);
        return createToken(oauthTokenReq);
    }

    @PostMapping("/login")
    @ApiOperation(value = "重新封装oauth2的接口实现", notes = "重新封装oauth2的接口实现")
    public Result<?> tokenPost(@RequestBody OauthTokenReq oauthTokenReq) {
        // 验证参数
        validation(oauthTokenReq);
        return createToken(oauthTokenReq);
    }

    /**
     * Get 方式获取认证信息
     * @param oauthTokenReq 请求参数
     * @return 返回同一响应对象
     */
    private Result<?> createToken(OauthTokenReq oauthTokenReq) {
        RestTemplate restTemplate = new RestTemplate();
        try {
            OauthClientDetails details = oauthClientDetailsService.getByClientId(oauthTokenReq.getClientId());
            // 数据库有数据并且跟前端传过来的秘钥匹配
            if (!(details != null && passwordEncoder.matches(oauthTokenReq.getClientSecret(), details.getClientSecret()))) {
                throw new CustomerException("请填写正确的客户端信息!");
            }
            restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(oauthTokenReq.getClientId(), oauthTokenReq.getClientSecret()));
            ObjectMapper mapper = new ObjectMapper();
            // 设置转json的时候驼峰转下划线
            mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
            String value = mapper.writeValueAsString(oauthTokenReq);
            JavaType javaType = mapper.getTypeFactory().constructParametricType(Map.class, String.class, String.class);
            Map<String, String> map = mapper.readValue(value, javaType);
            // map类型转换
            MultiValueMap<String, String> valueMap = new LinkedMultiValueMap<>();
            for (Map.Entry<String, String> entry : map.entrySet()) {
                valueMap.add(entry.getKey(), entry.getValue());
            }

            HttpServletRequest request = SpringContextUtils.getCurrentRequest();
            String requestMethod = request.getMethod();
            String body;
            String requestUrl = "http://localhost:" + serverProperties.getPort() +"/oauth/token";
            // 如果是GET请求
            if (HttpRequestMethod.GET.getMethod().equalsIgnoreCase(requestMethod)) {
                // 采用GET的方式请求
                body = restTemplate.getForObject(requestUrl, String.class, valueMap);
            } else {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                // 采用POST的方式请求
                body = restTemplate.postForObject(requestUrl, new HttpEntity<>(valueMap, headers), String.class);
            }
            Result<?> result = JsonUtils.jsonCovertToObject(body, Result.class);
            if (result != null && result.getCode() != null) {
                return ResultUtils.error(result.getMessage());
            }
            return ResultUtils.success(JsonUtils.jsonCovertToObject(body, OAuth2AccessToken.class));
        } catch (IOException e) {
            log.error("序列化json时失败! 原因: ", e);
            throw new CustomerException("请求参数有误, 序列化json时失败!");
        }
    }

    /**
     * 检验参数是否合法
     * @param oauthTokenReq 参数
     */
    private void validation(OauthTokenReq oauthTokenReq) {
        Assert.hasLength(oauthTokenReq.getClientId(), "clientId 不能为空");
        Assert.hasLength(oauthTokenReq.getClientSecret(), "clientSecret 不能为空");
        // 刷新token模式
        if (GrantTypeConstant.REFRESH_TOKEN.equalsIgnoreCase(oauthTokenReq.getGrantType())) {
            // refreshToken串, 刷新token时必须
            Assert.hasLength(oauthTokenReq.getRefreshToken(), "refreshToken 不能为空");
        } else if (GrantTypeConstant.PASSWORD.equalsIgnoreCase(oauthTokenReq.getGrantType())) {
            // 账号和密码在密码模式必须
            Assert.hasLength(oauthTokenReq.getUsername(), "username 不能为空");
            Assert.hasLength(oauthTokenReq.getPassword(), "password 不能为空");
        }
    }
}
